home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.3 (Developer)…68k, x86, SPARC, PA-RISC] / NeXTSTEP 3.3 Dev Intel.iso / NextDeveloper / Examples / AppKit / Draw / TextGraphic.m < prev    next >
Encoding:
Text File  |  1993-01-07  |  25.7 KB  |  951 lines

  1. #import "draw.h"
  2.  
  3. @implementation TextGraphic
  4. /*
  5.  * This uses a text object to draw and edit text.
  6.  *
  7.  * The one quirky thing to understand here is that growable Text objects
  8.  * in NeXTSTEP must be subviews of flipped view.  Since a GraphicView is not
  9.  * flipped, we must have a flipped view into the view heirarchy when we
  10.  * edit (this editing view is permanently installed as a subview of the
  11.  * GraphicView--see GraphicView's newFrame: method).
  12.  */
  13.  
  14. + initialize
  15. {
  16.     [TextGraphic setVersion:6];    /* class version, see read: */
  17.     return self;
  18. }
  19.  
  20. static Text *drawText = nil;    /* shared Text object used for drawing */
  21.  
  22. static void initClassVars()
  23. /*
  24.  * Create the class variable drawText here.
  25.  */
  26. {
  27.     if (!drawText) {
  28.     drawText = [Text new];
  29.     [drawText setMonoFont:NO];
  30.     [drawText setEditable:NO];
  31.     [drawText setSelectable:NO];
  32.     [drawText setFlipped:YES];
  33.     }
  34. }
  35.  
  36. + (BOOL)canInitFromPasteboard:(Pasteboard *)pboard
  37. {
  38.     return IncludesType([pboard types], NXRTFPboardType) ||
  39.        IncludesType([pboard types], NXAsciiPboardType);
  40. }
  41.  
  42. - init
  43. /*
  44.  * Creates a "blank" TextGraphic.
  45.  * This is TextGraphic's designated initializer,
  46.  * but be wary because by the time this returns, the
  47.  * TextGraphic may not be full initialized (it'll be
  48.  * valid, just perhaps not fully initialized).
  49.  * Override finishedWithInit if you want that.
  50.  */
  51. {
  52.     initClassVars();
  53.     [super init];
  54.     return self;
  55. }
  56.  
  57. - initEmpty
  58. /*
  59.  * Creates an empty TextGraphic.
  60.  */
  61. {
  62.     [self init];
  63.     return [self finishedWithInit];
  64. }
  65.  
  66. - finishedWithInit
  67. /*
  68.  * Override this if you want to know when a newly
  69.  * initialized TextGraphics is fully init'ed.
  70.  */
  71. {
  72.     return self;
  73. }
  74.  
  75. - doInitFromStream:(NXStream *)stream
  76. /*
  77.  * Common code for initFromStream: and reinitFromStream:.
  78.  * Looks at the first 5 characters of the stream and if it
  79.  * looks like an RTF file, then the contents of the stream
  80.  * are parsed as RTF, otherwise, the contents of the stream
  81.  * are assumed to be ASCII text and is passed through the
  82.  * drawText object and turned into RTF (using the method
  83.  * (writeRichText:).
  84.  */
  85. {
  86.     int maxlen;
  87.     char *buffer;
  88.  
  89.     if (stream) {
  90.     NXGetMemoryBuffer(stream, &buffer, &length, &maxlen);
  91.     if (!strncmp(buffer, "{\\rtf", 5)) {
  92.         NX_ZONEMALLOC([self zone], data, char, length);
  93.         bcopy(buffer, data, length);
  94.         [drawText readRichText:stream];
  95.     } else {
  96.         [drawText selectAll:self];
  97.         [drawText setFont:[Font userFontOfSize:-1.0 matrix:NX_FLIPPEDMATRIX]];
  98.         [drawText readText:stream];
  99.         stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  100.         [drawText writeRichText:stream];
  101.         NXGetMemoryBuffer(stream, &buffer, &length, &maxlen);
  102.         NX_ZONEMALLOC([self zone], data, char, length);
  103.         bcopy(buffer, data, length);
  104.         NXCloseMemory(stream, NX_FREEBUFFER);
  105.     }
  106.     [drawText setSel:0 :0];
  107.     font = [drawText font];
  108.     }
  109.  
  110.     return self;
  111. }
  112.  
  113. - initFromStream:(NXStream *)stream
  114. /*
  115.  * Initializes the TextGraphic using data from the passed stream.
  116.  */
  117. {
  118.     [self init];
  119.  
  120.     if (stream) {
  121.     [self doInitFromStream:stream];
  122.     [drawText setHorizResizable:YES];
  123.     [drawText setVertResizable:YES];
  124.     bounds.size.width = bounds.size.height = 10000.0;
  125.     [drawText setMaxSize:&bounds.size];
  126.     [drawText calcLine];
  127.     [drawText getMinWidth:&bounds.size.width minHeight:&bounds.size.height maxWidth:10000.0 maxHeight:10000.0];
  128.     bounds.origin.x = bounds.origin.y = 0.0;
  129.     }
  130.  
  131.     return [self finishedWithInit];
  132. }
  133.  
  134. - initFromFile:(const char *)file
  135. /*
  136.  * Initializes the TextGraphic using data from the passed file.
  137.  */
  138. {
  139.     TextGraphic *retval = nil;
  140.     NXStream *stream = NXMapFile(file, NX_READONLY);
  141.     retval = [self initFromStream:stream];
  142.     NXCloseMemory(stream, NX_FREEBUFFER);
  143.     return [retval finishedWithInit];
  144. }
  145.  
  146.  
  147. - initFromPasteboard:(Pasteboard *)pboard
  148. /*
  149.  * Initializes the TextGraphic using data from the passed Pasteboard.
  150.  */
  151. {
  152.     NXStream *stream;
  153.  
  154.     if (IncludesType([pboard types], NXRTFPboardType)) {
  155.     stream = [pboard readTypeToStream:NXRTFPboardType];
  156.     [self initFromStream:stream];
  157.     NXCloseMemory(stream, NX_FREEBUFFER);
  158.     } else if (IncludesType([pboard types], NXAsciiPboardType)) {
  159.     stream = [pboard readTypeToStream:NXAsciiPboardType];
  160.     [self initFromStream:stream];
  161.     NXCloseMemory(stream, NX_FREEBUFFER);
  162.     } else {
  163.     [self free];
  164.     return nil;
  165.     }
  166.  
  167.     return [self finishedWithInit];
  168. }
  169.  
  170. - (NXRect)reinitFromStream:(NXStream *)stream
  171. /*
  172.  * Reinitializes the TextGraphic from the data in the passed stream.
  173.  */
  174. {
  175.     NXRect ebounds;
  176.     [self doInitFromStream:stream];
  177.     [self getExtendedBounds:&ebounds];
  178.     return ebounds;
  179. }
  180.  
  181. - (NXRect)reinitFromFile:(const char *)file
  182. /*
  183.  * Reinitializes the TextGraphic from the data in the passed file.
  184.  */
  185. {
  186.     NXRect ebounds;
  187.     NXStream *stream = NXMapFile(file, NX_READONLY);
  188.     [self doInitFromStream:stream];
  189.     NXCloseMemory(stream, NX_FREEBUFFER);
  190.     [self getExtendedBounds:&ebounds];
  191.     return ebounds;
  192. }
  193.  
  194. - (NXRect)reinitFromPasteboard:(Pasteboard *)pboard
  195. /*
  196.  * Reinitializes the TextGraphic from the data in the passed Pasteboard.
  197.  */
  198. {
  199.     NXRect ebounds;
  200.     NXStream *stream;
  201.  
  202.     if (IncludesType([pboard types], NXRTFPboardType)) {
  203.     stream = [pboard readTypeToStream:NXRTFPboardType];
  204.     [self doInitFromStream:stream];
  205.     [self getExtendedBounds:&ebounds];
  206.     NXCloseMemory(stream, NX_FREEBUFFER);
  207.     } else if (IncludesType([pboard types], NXAsciiPboardType)) {
  208.     stream = [pboard readTypeToStream:NXAsciiPboardType];
  209.     [self doInitFromStream:stream];
  210.     [self getExtendedBounds:&ebounds];
  211.     NXCloseMemory(stream, NX_FREEBUFFER);
  212.     } else {
  213.     ebounds.origin.x = ebounds.origin.y = 0.0;
  214.     ebounds.size.width = ebounds.size.height = 0.0;
  215.     }
  216.  
  217.     return ebounds;
  218. }
  219.  
  220. - free
  221. {
  222.     free(data);
  223.     return [super free];
  224. }
  225.  
  226. /* Link methods */
  227.  
  228. - setLink:(NXDataLink *)aLink
  229. /*
  230.  * Note that we "might" be linked because even though we obviously
  231.  * ARE linked now, that might change in the future and the mightBeLinked
  232.  * flag is only advisory and is never cleared.  This is because during
  233.  * cutting and pasting, the TextGraphic might be linked, then unlinked,
  234.  * then linked, then unlinked and we have to know to keep trying to
  235.  * reestablish the link.  See readLinkForGraphic:... in gvLinks.m.
  236.  */
  237. {
  238.     NXDataLink *oldLink = link;
  239.     link = aLink;
  240.     gFlags.mightBeLinked = YES;
  241.     return oldLink;
  242. }
  243.  
  244. - (NXDataLink *)link
  245. {
  246.     return link;
  247. }
  248.  
  249. /* Form entry methods. */
  250.  
  251. /*
  252.  * Form Entries are essentially text items whose location, font, etc., are
  253.  * written out separately in an ASCII file when a Draw document is saved.
  254.  * When this is done, an EPS image of the Draw view is also written out
  255.  * (both of these files are place along with the document in the file package).
  256.  * These ASCII descriptions can then be used by other applications to overlay
  257.  * fields on top of a background of what is created by Draw.
  258.  *
  259.  * The most notable client of this right now is the Fax stuff.
  260.  */
  261.  
  262. - initFormEntry:(const char *)entryName localizable:(BOOL)isLocalizable
  263. /*
  264.  * The localizeFormEntry stuff is used by the Fax stuff in the following manner:
  265.  * If a form entry is localizable, then it appears in Draw in whatever the local
  266.  * language is, but, when written to the ASCII form.info file, it is written out
  267.  * not-localized.  Then, when the entity that reads the form.info file reads it,
  268.  * it is responsible for localizing it.  This enables the entity reading the
  269.  * form to actually semantically understand what a given form entry is (e.g. it
  270.  * is the To: field in a Fax Cover Sheet).
  271.  */ 
  272. {
  273.     char *buffer;
  274.     int maxlen;
  275.     NXStream *stream;
  276.  
  277.     [self init];
  278.     gFlags.isFormEntry = YES;
  279.     gFlags.localizeFormEntry = isLocalizable ? YES : NO;
  280.     bounds.size.width = 300.0;
  281.     bounds.size.height = 30.0;
  282.     [drawText setText:entryName];
  283.     [drawText setSel:0:100000];
  284.     [drawText setSelColor:NX_COLORBLACK];
  285.     [drawText setFont:[Font userFontOfSize:24.0 matrix:NX_FLIPPEDMATRIX]];
  286.     [drawText setHorizResizable:YES];
  287.     [drawText setVertResizable:YES];
  288.     bounds.size.width = bounds.size.height = 10000.0;
  289.     [drawText setMaxSize:&bounds.size];
  290.     [drawText calcLine];
  291.     [drawText getMinWidth:&bounds.size.width minHeight:&bounds.size.height maxWidth:10000.0 maxHeight:10000.0];
  292.     bounds.origin.x = bounds.origin.y = 0.0;
  293.     bounds.size.width = 300.0;
  294.     stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  295.     [drawText writeRichText:stream];
  296.     NXGetMemoryBuffer(stream, &buffer, &length, &maxlen);
  297.     NX_ZONEMALLOC([self zone], data, char, length);
  298.     bcopy(buffer, data, length);
  299.     NXCloseMemory(stream, NX_FREEBUFFER);
  300.     
  301.     return [self finishedWithInit];
  302. }
  303.  
  304. #define LOCAL_FORM_ENTRY(s) \
  305.     NXLoadLocalStringFromTableInBundle("CoverSheet", [NXBundle mainBundle], s, NULL)
  306. #define FORM_ENTRY_BUF_SIZE 100
  307.  
  308. - prepareFormEntry
  309. /*
  310.  * Loads up the drawText with all the right attributes to
  311.  * display a form entry.  Called from draw.
  312.  */
  313. {
  314.     NXCoord width, height;
  315.     char *s, buffer[FORM_ENTRY_BUF_SIZE];
  316.  
  317.     [drawText setTextGray:NX_LTGRAY];
  318.     [drawText setFont:[drawText font]];
  319.     [drawText setAlignment:NX_LEFTALIGNED];
  320.     [drawText getSubstring:buffer start:0 length:FORM_ENTRY_BUF_SIZE];
  321.     buffer[FORM_ENTRY_BUF_SIZE-1] = '\0';
  322.     if ((s = strchr(buffer, '\n')) || gFlags.localizeFormEntry) {
  323.     if (s) *s = '\0';
  324.     if (gFlags.localizeFormEntry) {
  325.         [drawText setText:LOCAL_FORM_ENTRY(buffer)];
  326.     } else {
  327.         [drawText setText:buffer];
  328.     }
  329.     }
  330.     [drawText setHorizResizable:YES];
  331.     [drawText setVertResizable:YES];
  332.     [drawText setMaxSize:&bounds.size];
  333.     [drawText calcLine];
  334.     [drawText getMinWidth:&width minHeight:&height maxWidth:10000.0 maxHeight:10000.0];
  335.     if (width > bounds.size.width) width = bounds.size.width;
  336.     if (height > bounds.size.height) height = bounds.size.height;
  337.     [drawText sizeTo:width :height];
  338.     [drawText moveTo:bounds.origin.x + floor((bounds.size.width - width) / 2.0)
  339.             :bounds.origin.y + floor((bounds.size.height - height) / 2.0)];
  340.  
  341.     return self;
  342. }
  343.  
  344. - (BOOL)isFormEntry
  345. {
  346.     return gFlags.isFormEntry;
  347. }
  348.  
  349. - setFormEntry:(int)flag
  350. {
  351.     gFlags.isFormEntry = flag ? YES : NO;
  352.     return self;
  353. }
  354.  
  355. - (Font *)getFormEntry:(char *)buffer andGray:(float *)gray
  356. /*
  357.  * Gets the information which will be written out into the
  358.  * form.info ASCII form entry description file.  Specifically,
  359.  * it gets the gray value, the actually name of the entry, and
  360.  * the Font of the entry.
  361.  */
  362. {
  363.     char *s;
  364.     NXStream *stream;
  365.  
  366.     if (gFlags.isFormEntry) {
  367.     stream = NXOpenMemory(data, length, NX_READONLY);
  368.     [drawText readRichText:stream];
  369.     [drawText setSel:0 :0];
  370.     if (gray) *gray = [drawText selGray];
  371.     NXCloseMemory(stream, NX_SAVEBUFFER);
  372.     [drawText getSubstring:buffer start:0 length:FORM_ENTRY_BUF_SIZE];
  373.     buffer[FORM_ENTRY_BUF_SIZE-1] = '\0';
  374.     if (s = strchr(buffer, '\n')) *s = '\0';
  375.     return [drawText font];
  376.     }
  377.  
  378.     return nil;
  379. }
  380.  
  381. - (BOOL)writeFormEntryToStream:(NXStream *)stream
  382. /*
  383.  * Writes out the ASCII representation of the location, gray,
  384.  * etc., of this form entry.  This is called only during
  385.  * the saving of a Draw document.
  386.  */
  387. {
  388.     Font *myFont;
  389.     float gray;
  390.     char buffer[FORM_ENTRY_BUF_SIZE];
  391.  
  392.     if (myFont = [self getFormEntry:buffer andGray:&gray]) {
  393.     NXPrintf(stream, "Entry: %s\n", buffer);
  394.     NXPrintf(stream, "Font: %s\n", [myFont name]);
  395.     NXPrintf(stream, "Font Size: %f\n", [myFont pointSize]);
  396.     NXPrintf(stream, "Text Gray: %f\n", gray);
  397.         NXPrintf(stream, "Location: x = %d, y = %d, w = %d, h = %d\n",
  398.         (int)bounds.origin.x, (int)bounds.origin.y,
  399.         (int)bounds.size.width, (int)bounds.size.height);
  400.     return YES;
  401.     }
  402.  
  403.     return NO;
  404. }
  405.  
  406. /* Factory methods overridden from superclass */
  407.  
  408. + (BOOL)isEditable
  409. {
  410.     return YES;
  411. }
  412.  
  413. + cursor
  414. {
  415.     return NXIBeam;
  416. }
  417.  
  418. /* Instance methods overridden from superclass */
  419.  
  420. - (const char *)title
  421. {
  422.     return NXLocalStringFromTable("Operations", "Text", NULL, "The %s of the `New %s' operation corresponding to creating an area for the user to type into.");
  423. }
  424.  
  425. - (BOOL)create:(NXEvent *)event in:(GraphicView *)view
  426.  /*
  427.   * We are only interested in where the mouse goes up, that's
  428.   * where we'll start editing.
  429.   */
  430. {
  431.     NXRect viewBounds;
  432.  
  433.     event = [NXApp getNextEvent:NX_MOUSEUPMASK];
  434.     bounds.size.width = bounds.size.height = 0.0;
  435.     bounds.origin = event->location;
  436.     [view convertPoint:&bounds.origin fromView:nil];
  437.     [view getBounds:&viewBounds];
  438.     gFlags.selected = NO;
  439.  
  440.     return NXMouseInRect(&bounds.origin, &viewBounds, NO);
  441. }
  442.  
  443. - (BOOL)edit:(NXEvent *)event in:(View *)view
  444. {
  445.     id change;
  446.     NXRect eb;
  447.  
  448.     if (gFlags.isFormEntry && gFlags.localizeFormEntry) return NO;
  449.     if ([self link]) return NO;
  450.  
  451.     editView = view;
  452.     graphicView = [editView superview];
  453.     
  454.     /* Get the field editor in this window. */
  455.  
  456.     if (gFlags.isFormEntry) {
  457.     gFlags.isFormEntry = NO;
  458.     [[view superview] cache:[self getExtendedBounds:&eb]];    // gFlags.isFormEntry starts editing
  459.     [[view window] flushWindow];
  460.     gFlags.isFormEntry = YES;
  461.     }
  462.  
  463.     change = [[StartEditingGraphicsChange alloc] initGraphic:self];
  464.     [change startChange];
  465.     [self prepareFieldEditor];
  466.     if (event) {  
  467.         [fe selectNull];    /* eliminates any existing selection */
  468.         [fe mouseDown:event]; /* Pass the event on to the Text object */
  469.     } 
  470.     [change endChange];
  471.  
  472.     return YES;
  473. }
  474.  
  475. - draw
  476.  /*
  477.   * If the region has already been created, then we must draw the text.
  478.   * To do this, we first load up the shared drawText Text object with
  479.   * our rich text.  We then set the frame of the drawText object
  480.   * to be our bounds.  Finally, we add the Text object as a subview of
  481.   * the view that is currently being drawn in ([NXApp focusView])
  482.   * and tell the Text object to draw itself.  We then remove the Text
  483.   * object view from the view heirarchy.
  484.   */
  485. {
  486.     NXStream *stream;
  487.  
  488.     if (data && (!gFlags.isFormEntry || NXDrawingStatus == NX_DRAWING)) {
  489.     stream = NXOpenMemory(data, length, NX_READONLY);
  490.     [drawText readRichText:stream];
  491.     NXCloseMemory(stream, NX_SAVEBUFFER);
  492.     if (gFlags.isFormEntry) {
  493.         [self prepareFormEntry];
  494.     } else {
  495.         [drawText setFrame:&bounds];
  496.     }
  497.     [[NXApp focusView] addSubview:drawText];
  498.     [drawText display];
  499.     [drawText removeFromSuperview];
  500.     if (DrawStatus == Resizing || gFlags.isFormEntry) {
  501.         PSsetgray(NX_LTGRAY);
  502.         NXFrameRect(&bounds);
  503.     }
  504.     }
  505.  
  506.     return self;
  507. }
  508.  
  509. - performTextMethod:(SEL)aSelector with:(void *)anArgument
  510. /*
  511.  * This performs the given aSelector on the text by loading up
  512.  * a Text object and applying aSelector to it (with selectAll:
  513.  * having been done first).  See PerformTextGraphicsChange.m
  514.  * in graphicsUndo.subproj.
  515.  */
  516. {
  517.     id change;
  518.  
  519.     if (data) {
  520.     change = [PerformTextGraphicsChange alloc];
  521.     [change initGraphic:self view:graphicView];
  522.     [change startChangeIn:graphicView];
  523.         [change loadGraphic];
  524.         [[change editText] perform:aSelector with:anArgument];
  525.         [change unloadGraphic];
  526.     [change endChange];
  527.     }
  528.  
  529.     return self;
  530. }
  531.  
  532. - setFont:aFont
  533. {
  534.     font = aFont;
  535.     return self;
  536. }
  537.  
  538. - (char *)data
  539. {
  540.     return data;
  541. }
  542.  
  543. - setData:(char *)newData
  544. {
  545.     if (data) NX_FREE(data);
  546.     data = newData;
  547.     return self;
  548. }
  549.  
  550. - (int)length
  551. {
  552.     return length;
  553. }
  554.  
  555. - setLength:(int)newLength
  556. {
  557.     length = newLength;
  558.     return self;
  559. }
  560.  
  561. - changeFont:sender
  562. {
  563.     [self performTextMethod:@selector(changeFont:) with:sender];
  564.     return self;
  565. }
  566.  
  567. - (Font *)font
  568. {
  569.     NXStream *stream;
  570.  
  571.     if (!font && data) {
  572.     stream = NXOpenMemory(data, length, NX_READONLY);
  573.     [drawText readRichText:stream];
  574.     NXCloseMemory(stream, NX_SAVEBUFFER);
  575.     [drawText setSel:0 :0];
  576.     font = [drawText font];
  577.     }
  578.  
  579.     return font;
  580. }
  581.  
  582. - (BOOL)isOpaque
  583. /*
  584.  * We are never opaque.
  585.  */
  586. {
  587.     return NO;
  588. }
  589.  
  590. - (BOOL)isValid
  591. /*
  592.  * Any size TextGraphic is valid (since we fix up the size if it is
  593.  * too small in our override of create:in:).
  594.  */
  595. {
  596.     return YES;
  597. }
  598.  
  599. - (NXColor)lineColor
  600. {
  601.     return NX_COLORBLACK;
  602. }
  603.  
  604. - (NXColor)fillColor
  605. {
  606.     return NX_COLORWHITE;
  607. }
  608.  
  609. - (NXCoord)baseline
  610. {
  611.     NXCoord ascender, descender, lineHeight;
  612.  
  613.     if (!font) [self font];
  614.     if (font) {
  615.     NXTextFontInfo(font, &ascender, &descender, &lineHeight);
  616.     return bounds.origin.y + bounds.size.height + ascender;
  617.     }
  618.  
  619.     return 0;
  620. }
  621.  
  622. - moveBaselineTo:(NXCoord *)y
  623. {
  624.     NXCoord ascender, descender, lineHeight;
  625.  
  626.     if (y && !font) [self font];
  627.     if (y && font) {
  628.     NXTextFontInfo(font, &ascender, &descender, &lineHeight);
  629.     bounds.origin.y = *y - ascender - bounds.size.height;
  630.     }
  631.  
  632.     return self;
  633. }
  634.  
  635. /* Public methods */
  636.  
  637. - prepareFieldEditor
  638. /*
  639.  * Here we are going to use the shared field editor for the window to
  640.  * edit the text in the TextGraphic.  First, we must end any other editing
  641.  * that is going on with the field editor in this window using endEditingFor:.
  642.  * Next, we get the field editor from the window.  Normally, the field
  643.  * editor ends editing when carriage return is pressed.  This is due to
  644.  * the fact that its character filter is NXFieldFilter.  Since we want our
  645.  * editing to be more like an editor (and less like a Form or TextField),
  646.  * we set the character filter to be NXEditorFilter.  What is more, normally,
  647.  * you can't change the font of a TextField or Form with the FontPanel
  648.  * (since that might interfere with any real editable Text objects), but
  649.  * in our case, we do want to be able to do that.  We also want to be
  650.  * able to edit rich text, so we issue a setMonoFont:NO.  Editing is a bit
  651.  * more efficient if we set the Text object to be opaque.  Note that
  652.  * in textDidEnd:endChar: we will have to set the character filter,
  653.  * FontPanelEnabled and mono-font back so that if there were any forms
  654.  * or TextFields in the window, they would have a correctly configured
  655.  * field editor.
  656.  *
  657.  * To let the field editor know exactly where editing is occurring and how
  658.  * large the editable area may grow to, we must calculate and set the frame
  659.  * of the field editor as well as its minimum and maximum size.
  660.  *
  661.  * We load up the field editor with our rich text (if any).
  662.  *
  663.  * Finally, we set self as the delegate (so that it will receive the
  664.  * textDidEnd:endChar: message when editing is completed) and either
  665.  * pass the mouse-down event onto the Text object, or, if a mouse-down
  666.  * didn't cause editing to occur (i.e. we just created it), then we
  667.  * simply put the blinking caret at the beginning of the editable area.
  668.  *
  669.  * The line marked with the "ack!" is kind of strange, but is necessary
  670.  * since growable Text objects only work when they are subviews of a flipped
  671.  * view.
  672.  *
  673.  * This is why GraphicView has an "editView" which is a flipped view that it
  674.  * inserts as a subview of itself for the purposes of providing a superview
  675.  * for the Text object.  The "ack!" line converts the bounds of the TextGraphic
  676.  * (which are in GraphicView coordinates) to the coordinates of the Text
  677.  * object's superview (the editView).  This limitation of the Text object
  678.  * will be fixed post-1.0.  Note that the "ack!" line is the only one
  679.  * concession we need to make to this limitation in this method (there is
  680.  * another such line in resignFieldEditor).
  681.  */
  682. {
  683.     NXSize maxSize;
  684.     NXStream *stream;
  685.     NXRect viewBounds, frame, eb;
  686.  
  687.     [NXApp sendAction:@selector(disableChanges:) to:nil from:self];
  688.     [[graphicView window] endEditingFor:self];
  689.     fe = [[graphicView window] getFieldEditor:YES for:self];
  690.     
  691.     if ([self isSelected]) {
  692.         [self deselect];
  693.         [graphicView cache:[self getExtendedBounds:&eb] andUpdateLinks:NO];
  694.         [[graphicView selectedGraphics] removeObject:self];
  695.     }
  696.     
  697.     [fe setFont:[[FontManager new] selFont]];
  698.     
  699.     /* Modify it so that it will edit Rich Text and use the FontPanel. */
  700.     
  701.     [fe setCharFilter:NXEditorFilter];
  702.     [fe setFontPanelEnabled:YES];
  703.     [fe setMonoFont:NO];
  704.     [fe setOpaque:YES];
  705.     
  706.     /*
  707.         * Determine the minimum and maximum size that the Text object can be.
  708.         * We let the Text object grow out to the edges of the GraphicView,
  709.         * but no further.
  710.         */
  711.     
  712.     [editView getBounds:&viewBounds];
  713.     maxSize.width = viewBounds.origin.x+viewBounds.size.width-bounds.origin.x;
  714.     maxSize.height = bounds.origin.y+bounds.size.height-viewBounds.origin.y;
  715.     if (!bounds.size.height && !bounds.size.width) {
  716.         bounds.origin.y -= floor([fe lineHeight] / 2.0);
  717.         bounds.size.height = [fe lineHeight];
  718.         bounds.size.width = 5.0;
  719.     }
  720.     frame = bounds;
  721.     [editView convertRect:&frame fromView:graphicView];    // ack!
  722.     [fe setMinSize:&bounds.size];
  723.     [fe setMaxSize:&maxSize];
  724.     [fe setFrame:&frame];
  725.     [fe setVertResizable:YES];
  726.     
  727.     /*
  728.         * If we already have text, then put it in the Text object (allowing
  729.         * the Text object to grow downward if necessary), otherwise, put
  730.         * no text in, set some initial parameters, and allow the Text object
  731.         * to grow horizontally as well as vertically
  732.         */
  733.     
  734.     if (data) {
  735.         [fe setHorizResizable:NO];
  736.         stream = NXOpenMemory(data, length, NX_READONLY);
  737.         [fe readRichText:stream];
  738.         NXCloseMemory(stream, NX_SAVEBUFFER);
  739.     } else {
  740.         [fe setHorizResizable:YES];
  741.         [fe setText:""];
  742.         [fe setAlignment:NX_LEFTALIGNED];
  743.         [fe setSelColor:NX_COLORBLACK];
  744.         [fe unscript:self];
  745.     }
  746.     
  747.     /*
  748.         * Add the Text object to the view heirarchy and set self as its delegate
  749.         * so that we will receive the textDidEnd:endChar: message when editing
  750.         * is finished.
  751.         */
  752.     
  753.     [fe setDelegate:self];
  754.     [editView addSubview:fe];
  755.     
  756.     /*
  757.         * Make it the first responder.
  758.         */
  759.     
  760.     [[graphicView window] makeFirstResponder:fe];
  761.     
  762.     /* Change the ruler to be a text ruler. */
  763.     
  764.     [fe tryToPerform:@selector(showTextRuler:) with:fe];
  765.     
  766.     [fe setSel:0:0];
  767.     [NXApp sendAction:@selector(enableChanges:) to:nil from:self];
  768.  
  769.     return self;
  770. }
  771.  
  772. - resignFieldEditor
  773. /* 
  774.  * We must extract the rich text the user has typed from the Text object,
  775.  * and store it away. We also need to get the frame of the Text object
  776.  * and make that our bounds (but, remember, since the Text object must
  777.  * be a subview of a flipped view, we need to convert the bounds rectangle
  778.  * to the coordinates of the unflipped GraphicView).  If the Text object
  779.  * is empty, then we remove this TextGraphic from the GraphicView.
  780.  * We must remove the Text object from the view heirarchy and, since
  781.  * this Text object is going to be reused, we must set its delegate
  782.  * back to nil.
  783.  *
  784.  * For further explanation of the "ack!" line, see edit:in: above.
  785.  */
  786. {
  787.     int maxlen;
  788.     char *buffer;
  789.     NXStream *stream;
  790.     NXRect oldBounds, *redrawRect = NULL;
  791.  
  792.     [NXApp sendAction:@selector(disableChanges:) to:nil from:self];
  793.     if (data) {
  794.         NX_FREE(data);
  795.         data = NULL;
  796.         length = 0;
  797.     }
  798.     
  799.     NX_ASSERT(editView == [fe superview], "Fault in Text Graphic: Code 2");
  800.     NX_ASSERT(graphicView == [editView superview], "Fault in Text Graphic: Code 3");
  801.     
  802.     if ([fe textLength]) {
  803.         stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  804.         [fe writeRichText:stream];
  805.         NXGetMemoryBuffer(stream, &buffer, &length, &maxlen);
  806.         NX_ZONEMALLOC([self zone], data, char, length);
  807.         bcopy(buffer, data, length);
  808.         NXCloseMemory(stream, NX_FREEBUFFER);
  809.         oldBounds = bounds;
  810.         [fe getFrame:&bounds];
  811.         [editView convertRect:&bounds toView:graphicView];    // ack!
  812.         NXUnionRect(&bounds, &oldBounds);
  813.         redrawRect = &oldBounds;
  814.     }
  815.  
  816.     if (redrawRect) [[graphicView window] disableFlushWindow];
  817.  
  818.     [graphicView tryToPerform:@selector(hideRuler:) with:nil];
  819.     [fe removeFromSuperview];
  820.     [fe setDelegate:nil];
  821.     [fe setSel:0 :0];
  822.     font = [fe font];
  823.     
  824.     if (redrawRect) {
  825.             [graphicView cache:redrawRect];
  826.         [[graphicView window] reenableFlushWindow];
  827.         [[graphicView window] flushWindow];
  828.     }
  829.  
  830.     fe = nil;
  831.     [NXApp sendAction:@selector(enableChanges:) to:nil from:self];
  832.     
  833.     return self;
  834. }
  835.  
  836. - (BOOL)isEmpty
  837. {
  838.     return data ? NO : YES;
  839. }
  840.  
  841. /* Text object delegate methods */
  842.  
  843. /*
  844.  * If we have more than one line, turn off horizontal resizing.
  845.  */
  846. - textDidResize:textObject oldBounds:(const NXRect *)oldBounds invalid:(NXRect *)invalidRect
  847. {
  848.     NXSelPt start,end;
  849.  
  850.     [textObject getSel:&start :&end];
  851.     if (start.line || end.line)
  852.       [textObject setHorizResizable:NO];
  853.     return self;
  854. }
  855.  
  856. - textDidEnd:textObject endChar:(unsigned short)endChar
  857. /*
  858.  * This method is called when ever first responder is taken away from a
  859.  * currently editing TextGraphic (i.e. when the user is done editing and
  860.  * chooses to go do something else).  
  861.  */
  862. {
  863.     id change;
  864.  
  865.     NX_ASSERT(fe == textObject, "Fault in Text Graphic: Code 1")
  866.     
  867.     change = [[EndEditingGraphicsChange alloc] initGraphicView:graphicView  graphic:self];
  868.     [change startChange];
  869.         [self resignFieldEditor];
  870.     if ([self isEmpty])
  871.         [graphicView removeGraphic:self];
  872.     [change endChange];
  873.  
  874.     return self;
  875. }
  876.  
  877. /* Archiving methods */
  878.  
  879. - awake
  880. {
  881.     initClassVars();
  882.     return [super awake];
  883. }
  884.  
  885. - write:(NXTypedStream *)stream
  886.  /*
  887.   * Writes the TextGraphic out to the typed stream.
  888.   */
  889. {
  890.     [super write:stream];
  891.     NXWriteTypes(stream, "i", &length);
  892.     NXWriteArray(stream, "c", length, data);
  893.     return self;
  894. }
  895.  
  896. - read:(NXTypedStream *)stream
  897.  /*
  898.   * Reads the TextGraphic in from the typed stream.
  899.   * This is versioned.  The old way we used to implement
  900.   * this class included using a Cell object.  Now we
  901.   * use the Text object directly.
  902.   */
  903. {
  904.     int version;
  905.  
  906.     version = NXTypedStreamClassVersion(stream, "TextGraphic");
  907.     [super read:stream];
  908.  
  909.     if (version < 1) {
  910.     Cell *cell;
  911.     int maxlen;
  912.     NXStream *s;
  913.     char *buffer;
  914.     NXReadTypes(stream, "@", &cell);
  915.     [drawText setText:[cell stringValue]];
  916.     font = [cell font];
  917.     [drawText setFont:[cell font]];
  918.     [drawText setTextColor:[self lineColor]];
  919.     s = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  920.     [drawText writeRichText:s];
  921.     NXGetMemoryBuffer(s, &buffer, &length, &maxlen);
  922.     NX_ZONEMALLOC([self zone], data, char, length);
  923.     bcopy(buffer, data, length);
  924.     NXCloseMemory(s, NX_FREEBUFFER);
  925.     } else {
  926.     NXReadTypes(stream, "i", &length);
  927.     NX_ZONEMALLOC([self zone], data, char, length);
  928.     NXReadArray(stream, "c", length, data);
  929.     }
  930.  
  931.     if (version > 2 && version < 5) {
  932.     int linkNumber;
  933.     NXReadTypes(stream, "i", &linkNumber);
  934.     } else if (version == 2) {
  935.     int linkNumber;
  936.     link = NXReadObject(stream);
  937.     linkNumber = [link linkNumber];
  938.     link = nil;
  939.     }
  940.  
  941.     if (version > 3 && version < 6) {
  942.     BOOL isFormEntry;
  943.     NXReadTypes(stream, "c", &isFormEntry);
  944.     gFlags.isFormEntry = isFormEntry ? YES : NO;
  945.     }
  946.  
  947.     return self;
  948. }
  949.  
  950. @end
  951.